﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using CoreEx.Database.Metadata;
using LinFu.IoC.Configuration;
using LinFu.IoC.Interfaces;
using LinFu.IoC;

namespace CoreEx.Database.Linq.Implementation
{
    /// <summary>
    /// Extracts a set of load expressions
    /// from an expression that contains a <see cref="Queryable"/> instance implementing <see cref="ILoadOptions"/>.
    /// </summary>
    [Implements(typeof(IRelationLoadTargetExtractor))]
    public class LoadOptionExtractor : IRelationLoadTargetExtractor, IInitialize
    {
        private ITypeMappingRepository _mappingRepository;


        /// <summary>
        /// Initializes a new instance of the <see cref="LoadOptionExtractor"/> class.
        /// </summary>
        /// <param name="mappingRepository">The mapping repository.</param>
        public LoadOptionExtractor(ITypeMappingRepository mappingRepository)
        {
            _mappingRepository = mappingRepository;
        }


        /// <summary>
        /// Extracts the load expressions from a <see cref="Queryable"/> instance
        /// </summary>
        /// <param name="expression">The target expression.</param>
        /// <returns>
        /// An <see cref="IDictionary{TKey,TValue}"/> that
        /// contains a list of <see cref="IRelationLoadTarget"/> instances per target <see cref="Type"/>.
        /// </returns>
        public IDictionary<Type, IList<IRelationLoadTarget>> ExtractFrom(Expression expression)
        {
            var queryable = QueryableResolver.ResolveFrom(expression);
            var result = new Dictionary<Type, IList<IRelationLoadTarget>>();
            if (queryable == null)
                return result;
            var loadExpressions = ((ILoadOptions)queryable).Expressions;
            
            foreach (var loadExpression in loadExpressions)
            {
                var lambdaExpression = ((LambdaExpression) loadExpression.StripQuotes()).Evaluate();
                var loadExpressionTargetType = GetTargetType(lambdaExpression);
                if (!result.ContainsKey(loadExpressionTargetType))
                    result.Add(loadExpressionTargetType,new List<IRelationLoadTarget>());
                RelationLoadTarget relationLoadTarget = new RelationLoadTarget();
                relationLoadTarget.TargetProperty = GetTargetPropertyFromLoadExpression(lambdaExpression.Body);
                relationLoadTarget.LoadExpression = lambdaExpression;
                relationLoadTarget.PropertyMapping =
                    _mappingRepository[relationLoadTarget.TargetProperty.DeclaringType].ColumnMappings[
                        relationLoadTarget.TargetProperty];
                result[loadExpressionTargetType].Add(relationLoadTarget);
            }
            return result;
        }


        /// <summary>
        /// Gets or sets the <see cref="IQueryableResolver"/> instance that is responsible 
        /// of finding the <see cref="Queryable"/> instance from an <see cref="Expression"/> instance.
        /// </summary>        
        public IQueryableResolver QueryableResolver { get; set; }


        public void Initialize(IServiceContainer source)
        {
            QueryableResolver = source.GetService<IQueryableResolver>();
        }



        
        private Type GetTargetType(LambdaExpression lambdaExpression)
        {
            var funcDelegateType = lambdaExpression.GetType().GetGenericArguments().FirstOrDefault();
            var loadExpressionTargetType = funcDelegateType.GetGenericArguments().FirstOrDefault();
            return loadExpressionTargetType;    
        }

        private PropertyInfo GetTargetPropertyFromLoadExpression(Expression expression)
        {
            MemberExpression memberExpression = null;

            if (expression is MemberExpression)
                memberExpression = (MemberExpression) expression;
            
            if (expression is MethodCallExpression)
            {
                var methodCallExpression = (MethodCallExpression)expression;
                memberExpression = (MemberExpression)methodCallExpression.Arguments[0];
            }
            if (memberExpression != null)
                return (PropertyInfo) memberExpression.Member;
            return null;
        }


        private bool RepresentsEnumerableMember(LambdaExpression lambdaExpression)
        {
            return false;
        }
    }
}
